2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 Loads external plugins (Including plugins stored within our application bundle). Also responsible for warning the
21 user of old or incompatible plugins.
25 #import <Adium/AICorePluginLoader.h>
26 #import <AIUtilities/AIFileManagerAdditions.h>
27 #import <AIUtilities/AIApplicationAdditions.h>
28 #import <Adium/AIPlugin.h>
30 #define DIRECTORY_INTERNAL_PLUGINS [@"Contents" stringByAppendingPathComponent:@"PlugIns"] //Path to the internal plugins
31 #define EXTERNAL_PLUGIN_FOLDER @"PlugIns" //Folder name of external plugins
32 #define EXTERNAL_DISABLED_PLUGIN_FOLDER @"PlugIns (Disabled)" //Folder name for disabled external plugins
33 #define EXTENSION_ADIUM_PLUGIN @"AdiumPlugin" //File extension of a plugin
35 #define CONFIRMED_PLUGINS @"Confirmed Plugins"
36 #define CONFIRMED_PLUGINS_VERSION @"Confirmed Plugin Version"
38 @interface AICorePluginLoader (PRIVATE)
40 + (BOOL)confirmPluginAtPath:(NSString *)pluginPath;
41 + (void)disablePlugin:(NSString *)pluginPath;
44 @implementation AICorePluginLoader
48 if ((self = [super init])) {
49 pluginArray = [[NSMutableArray alloc] init];
61 [adium createResourcePathForName:EXTERNAL_PLUGIN_FOLDER];
63 //If the Adium version has changed since our last run, warn the user that their external plugins may no longer work
64 NSString *lastVersion = [[NSUserDefaults standardUserDefaults] objectForKey:CONFIRMED_PLUGINS_VERSION];
65 if (![[NSApp applicationVersion] isEqualToString:lastVersion]) {
66 [[NSUserDefaults standardUserDefaults] removeObjectForKey:CONFIRMED_PLUGINS];
67 [[NSUserDefaults standardUserDefaults] setObject:[NSApp applicationVersion] forKey:CONFIRMED_PLUGINS_VERSION];
70 NSEnumerator *enumerator = [[adium allResourcesForName:EXTERNAL_PLUGIN_FOLDER
71 withExtensions:EXTENSION_ADIUM_PLUGIN] objectEnumerator];
74 //Load any external plugins the user has installed
75 while ((path = [enumerator nextObject])) {
76 [[self class] loadPluginAtPath:path confirmLoading:YES pluginArray:pluginArray];
79 NSString *internalPluginsPath = [[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:DIRECTORY_INTERNAL_PLUGINS] stringByExpandingTildeInPath];
80 //Load the plugins in our bundle
81 enumerator = [[[NSFileManager defaultManager] directoryContentsAtPath:internalPluginsPath] objectEnumerator];
82 while ((path = [enumerator nextObject])) {
83 if ([[path pathExtension] caseInsensitiveCompare:EXTENSION_ADIUM_PLUGIN] == 0)
84 [[self class] loadPluginAtPath:[internalPluginsPath stringByAppendingPathComponent:path]
86 pluginArray:pluginArray];
90 - (void)controllerDidLoad
94 //Give all external plugins a chance to close
95 - (void)controllerWillClose
97 NSEnumerator *enumerator = [pluginArray objectEnumerator];
100 while ((plugin = [enumerator nextObject])) {
101 [[adium notificationCenter] removeObserver:plugin];
102 [[NSNotificationCenter defaultCenter] removeObserver:plugin];
103 [plugin uninstallPlugin];
109 [pluginArray release];
116 * @brief Load plugins from the specified path
118 * @param pluginPath The path to the plugin bundle
119 * @param confirmLoading If YES, confirm loading of the plugin if it hasn't been loaded with this Adium version before
120 * @param inPluginArray May be nil. If non-nil, an NSMutableArray to fill with an instance of the principal class (AIPlugin conforming) of each plugin which loads.
122 + (void)loadPluginAtPath:(NSString *)pluginPath confirmLoading:(BOOL)confirmLoading pluginArray:(NSMutableArray *)inPluginArray
124 BOOL loadPlugin = YES;
126 //Confirm the presence of external plugins with the user
127 if (confirmLoading) {
128 loadPlugin = [self confirmPluginAtPath:pluginPath];
133 NSBundle *pluginBundle;
134 id <AIPlugin> plugin = nil;
138 if ((pluginBundle = [NSBundle bundleWithPath:pluginPath])) {
139 Class principalClass = [pluginBundle principalClass];
140 if (principalClass) {
141 plugin = [[principalClass alloc] init];
143 NSLog(@"Failed to obtain principal class from plugin \"%@\" (\"%@\")! infoDictionary: %@",
144 [pluginPath lastPathComponent],
146 [pluginBundle infoDictionary]);
150 [plugin installPlugin];
151 [inPluginArray addObject:plugin];
154 NSLog(@"Failed to initialize Plugin \"%@\" (\"%@\")!",[pluginPath lastPathComponent],pluginPath);
157 NSLog(@"Failed to open Plugin \"%@\"!",[pluginPath lastPathComponent]);
162 if (confirmLoading) {
163 //The plugin encountered an exception while it was loading. There is no reason to leave this old
164 //or poorly coded plugin enabled so that it can cause more problems, so disable it and inform
165 //the user that they'll need to restart.
166 [self disablePlugin:pluginPath];
167 NSRunCriticalAlertPanel([NSString stringWithFormat:@"Error loading %@",[[pluginPath lastPathComponent] stringByDeletingPathExtension]],
168 @"An external plugin failed to load and has been disabled. Please relaunch Adium",
172 [NSApp terminate:nil];
178 //Confirm the presence of an external plugin with the user. Returns YES if the plugin should be loaded.
179 + (BOOL)confirmPluginAtPath:(NSString *)pluginPath
181 BOOL loadPlugin = YES;
182 NSArray *confirmed = [[NSUserDefaults standardUserDefaults] objectForKey:CONFIRMED_PLUGINS];
184 if (![[NSUserDefaults standardUserDefaults] boolForKey:@"AIAutoConfirmExternalPlugins"] &&
185 (!confirmed || ![confirmed containsObject:[pluginPath lastPathComponent]])) {
186 if (NSRunInformationalAlertPanel([NSString stringWithFormat:@"Disable %@?",[[pluginPath lastPathComponent] stringByDeletingPathExtension]],
187 @"External plugins may cause crashes and odd behavior after updating Adium. Disable this plugin if you experience any issues.",
190 nil) == NSAlertDefaultReturn) {
191 //Disable this plugin
192 [self disablePlugin:pluginPath];
196 //Add this plugin to our confirmed list
197 confirmed = (confirmed ? [confirmed arrayByAddingObject:[pluginPath lastPathComponent]] : [NSArray arrayWithObject:[pluginPath lastPathComponent]]);
198 [[NSUserDefaults standardUserDefaults] setObject:confirmed forKey:CONFIRMED_PLUGINS];
205 //Move a plugin to the disabled plugins folder
206 + (void)disablePlugin:(NSString *)pluginPath
208 NSString *pluginName = [pluginPath lastPathComponent];
209 NSString *basePath = [pluginPath stringByDeletingLastPathComponent];
210 NSString *disabledPath = [[basePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:EXTERNAL_DISABLED_PLUGIN_FOLDER];
212 [[NSFileManager defaultManager] createDirectoriesForPath:disabledPath];
213 [[NSFileManager defaultManager] movePath:[basePath stringByAppendingPathComponent:pluginName]
214 toPath:[disabledPath stringByAppendingPathComponent:pluginName]